// 声明包名为 gguf
package llama

// 导入必要的包
import (
	"Lunar-Astral-API/local_server/config" // 导入项目配置模块（如路径、端口等）
	"log"                                  // 标准日志包，用于输出调试/错误信息
	"os"                                   // 提供操作系统相关功能（如文件操作）
	"os/exec"                              // 执行外部命令（启动 GGUF 服务进程）
	"path/filepath"                        // 处理文件路径（如通配符匹配 .gguf 文件）
	"regexp"                               // 正则表达式处理
)

// PrintEndpoints 函数用于打印GGUF服务的各个接口地址，
func PrintEndpoints() {
	// 打印聊天补全接口地址
	log.Printf("GGUF服务 -> 聊天补全接口: http://localhost:%d/v1/chat/completions", *config.SystemUrlPort)
	// 打印文本补全接口地址
	log.Printf("GGUF服务 -> 文本补全接口: http://localhost:%d/v1/completions", *config.SystemUrlPort)
	// 打印嵌入服务接口地址
	log.Printf("GGUF服务 -> 嵌入服务接口: http://localhost:%d/v1/embeddings", *config.SystemUrlPort)
	// 打印模型列表接口地址
	log.Printf("GGUF服务 -> 模型列表接口: http://localhost:%d/v1/models", *config.SystemUrlPort)
}

/**
 * @description: 注册模型端口到全局映射
 * @description: 会对模型类型添加 "system-" 前缀后作为键，端口号作为值存储到映射中
 * @description: 为保证并发安全，操作前会对映射加锁，操作完成后自动解锁
 * @param {string} modelType 模型的类型，如 "embedding", "reasoning", "visual" 等
 * @param {int} port 该模型对应的服务端口号
 * @return {*} 无返回值
 */
func registerModelPort(modelType string, port int) {
	// 加锁，保证对全局映射的并发安全访问
	config.ModelMapMutex.Lock()
	// 函数返回前自动解锁
	defer config.ModelMapMutex.Unlock()
	// 将模型类型添加 "system-" 前缀后作为键，端口号作为值存储到全局映射中
	config.ModelPortMap["system-"+modelType] = port
}

/**
 * @description: 等待命令对应的进程退出，并在进程异常退出时记录错误日志
 * @param {*exec.Cmd} cmd 要等待退出的命令对象
 * @param {string} modelType 模型类型，如 "embedding", "reasoning", "visual" 等
 * @param {int} port 该模型对应的服务端口号
 * @return {*} 无返回值
 */
func waitForProcessExit(cmd *exec.Cmd, modelType string, port int) {
	// 等待命令对应的进程退出
	if err := cmd.Wait(); err != nil {
		// 若进程异常退出，记录错误日志，包含模型类型、端口号和错误信息
		log.Printf("GGUF服务[ERROR] -> 实例 %s (端口 %d) 退出: %v", modelType, port, err)
	}
}

/**
 * @description: 获取模型类型对应的端口号偏移量
 * @param {string} modelType 模型的类型，如 "embedding", "reasoning", "visual" 等
 * @return {int} 模型类型对应的端口号偏移量
 */
func getPortOffset(modelType string) int {
	// 根据不同的模型类型返回对应的端口偏移量
	switch modelType {
	case "embedding":
		// 文本嵌入模型，端口偏移量为 0
		return 0
	case "reasoning":
		// 推理模型，端口偏移量为 1
		return 1
	case "visualing":
		// 视觉模型，端口偏移量为 2
		return 2
	default:
		// 未知类型模型，默认端口偏移量为 0
		return 9
	}
}

/**
 * @description: 获取视觉模型所需的 mmproj 文件路径
 * @param {string} modelFile 视觉模型文件的路径
 * @return {string} 找到的 mmproj 文件路径，若未找到则返回空字符串
 */
func getMmprojPath(modelFile string) string {
	// 预编译正则表达式，匹配以 mmproj 开头（不区分大小写）的 .gguf 文件
	mmprojRegex := regexp.MustCompile(`(?i)^mmproj.*\.gguf$`)
	// 优先在模型所在目录查找
	modelDir := filepath.Dir(modelFile)
	if path := findMmprojInDir(modelDir, mmprojRegex); path != "" {
		return path
	}
	// 其次在默认模型目录查找
	if *config.ModelFilePath != "" && *config.ModelFilePath != modelDir {
		if path := findMmprojInDir(*config.ModelFilePath, mmprojRegex); path != "" {
			return path
		}
	}
	// 最后尝试在默认模型目录的 visual 子目录中查找
	visualDir := filepath.Join(*config.ModelFilePath, "visual")
	if visualDir != modelDir {
		if path := findMmprojInDir(visualDir, mmprojRegex); path != "" {
			return path
		}
	}
	return ""
}

/**
 * @description: 在指定目录中查找匹配 mmproj 正则表达式的文件
 * @param {string} dir 目录路径
 * @param {*regexp.Regexp} regex 预编译的正则表达式
 * @return {string} 第一个匹配的文件路径，若无则返回空字符串
 */
func findMmprojInDir(dir string, regex *regexp.Regexp) string {
	entries, err := os.ReadDir(dir)
	if err != nil {
		return ""
	}
	for _, entry := range entries {
		if !entry.IsDir() && regex.MatchString(entry.Name()) {
			return filepath.Join(dir, entry.Name())
		}
	}
	return ""
}
